#include "stdlib.h"
#include "stdio.h"
#include "httpd.h"
#include "pthread.h"
#include "lwip/sockets.h"

/**********************************************************************/
/* Print out an error message with perror() (for system errors; based
 * on value of errno, which indicates system call errors) and exit the
 * program indicating an error. */
/**********************************************************************/
void error_die(const char *sc)
{
    // #include <stdio.h>,void perror(char *string);
    // 在输出错误信息前加上字符串sc:
    perror(sc);
    exit(1);
}


/**********************************************************************/
/* Give a connfd a 404 not found status message. */
/**********************************************************************/
void send_index(int connfd)
{
    send(connfd, data_index_html, sizeof(data_index_html), 0);
    printf("index\n");
}

void send_photo(int connfd)
{
    send(connfd, data_photo_jpeg, sizeof(data_photo_jpeg), 0);
    printf("photo\n");
}


void send_favicon(int connfd)
{
    send(connfd, data_favicon_ico, sizeof(data_favicon_ico), 0);
    printf("favicon\n");
}
/**********************************************************************/
/* 这个函数在指定的端口上启动一个监听进程，如果端口是0，就动态分配一个
 * 并把值赋给变量port
 *
 * Parameters: pointer to variable containing the port to connect on
 * Returns: the socket */
/**********************************************************************/
int startup(unsigned short *port)
{
    int httpd = 0;
    struct sockaddr_in name;

    //建立TCP SOCKET,PF_INET等同于AF_INET
    httpd = socket(PF_INET, SOCK_STREAM, 0);
    if (httpd == -1)
        error_die("socket");

    //在修改源码后重启启动总是提示bind: Address already in use,使用tcpreuse解决
    int reuse = 1;
    if (setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        error_die("setsockopet");
    }

    memset(&name, 0, sizeof(name));
    name.sin_family = AF_INET;

    //<arpa/inet.h>，将*port 转换成以网络字节序表示的16位整数
    //这里port已被我修改为8008,如果为0的话内核会在bind时自动分配一个端口号
    name.sin_port = htons(*port);
    name.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
        error_die("bind");

    //这里是原来的代码逻辑,如果port是0的话通过bind后内核会随机分配一个端口号给当前的socket连接,
    //获取这个端口号给port变量
    if (*port == 0)  /* if dynamically allocating a port */
    {
        int namelen = sizeof(name);
        if (getsockname(httpd, (struct sockaddr *)&name, (socklen_t *)&namelen) == -1)
            error_die("getsockname");
        *port = ntohs(name.sin_port);
    }


    //启动监听，backlog为5
    if (listen(httpd, 5) < 0)
        error_die("listen");
    return (httpd);
}

/**********************************************************************/
/* 从socket读取一行。
 * 如果行以换行(\n)或者回车(\r)或者CRLF(\r\n)做为结束,就使用'\0'停止字符串读取
 * 如果buffer读取完都没有发现换行符,使用'\0'结束字符串
 * 如果上边三个任意一个行终止符被读出,都会被替换为\n,并且在末尾补充'\0'
 * 意思就是不管换行符是\r还是\n还是\r\n，都会被替换为\n\0
 *
 * 关于回车和换行：
 * '\r'是回车，使光标到行首，（carriage return）
 * '\n'是换行，使光标下移一格，（line feed / newline）
 * '\r\n'就是CRLF啦
 *
 * Parameters: the socket descriptor
 *             the buffer to save the data in
 *             the size of the buffer
 * Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0'; //补充到结尾的字符
    int n;

    while ((i < size - 1) && (c != '\n')) //因为字符串的最后一位要使用'\0'结束，所以size需要 -1
    {
        //从sock中一次读一个字符，循环读
        //int recv(int s, void *buf, int len, unsigned int flags);
        //recv()用来接收远端主机经指定的socket传来的数据, 并把数据存到由参数buf指向的内存空间, 参数len为可接收数据的最大长度.
        n = recv(sock, &c, 1, 0);
        if (n > 0) {
            if (c == '\r') { //这个if里边处理了\r和\r\n结尾的情况,把c统一改为\n,如果是\n结尾那无需处理直接就赋值给buf啦

                //参数 flags 一般设0，MSG_PEEK表示返回来的数据并不会在系统内删除, 如果再调用recv()会返回相同的数据内容.
                //这个选项用于测试下一个字符是不是\n，并不会把当前读出的字符从缓冲区中删掉
                n = recv(sock, &c, 1, MSG_PEEK);
                /* DEBUG printf("%02X\n", c); */
                if ((n > 0) && (c == '\n')) //如果是\n，就把下个字符读取出来,如果不是\n就把\r改为\n
                    recv(sock, &c, 1, 0);
                else
                    c = '\n';
            }

            buf[i] = c;
            i++;
        } else {
            //读取失败直接赋值\n
            c = '\n';
        }
    }

    //读取完一行数据后在获得的字符串末尾补上\0
    buf[i] = '\0';

    return (i);
}

/**********************************************************************/
/* A request has caused a call to accept() on the server port to
 * return.  Process the request appropriately.
 * Parameters: the socket connected to the connfd */
/**********************************************************************/
void accept_request(int connfd)
{
    char buf[1024]; //缓冲从socket中读取的字节
    int numchars; //读取字节数
    int n = 0;


    //从socket中读取一行数据
    //这里就是读取请求行(GET /index.html HTTP/1.1)，行数据放到buf中，长度返回给numchars
    //numchars = get_line(connfd, buf, sizeof(buf));

    numchars = recv(connfd, buf, sizeof(buf), 0);
    printf("%d data receive\n", numchars);
    for(n = 0; n < numchars; n++)
    {
        printf("%c", buf[n]);
    }
    //printf("%02x   %02x\n", buf[4], buf[5]);
    printf("\n");
    if(buf[4] == 0x2f && buf[5] == 0x20)
        send_index(connfd);
    else if(buf[4] == 0x2f && buf[5] == 0x70)
        send_photo(connfd);
    else if(buf[4] == 0x2f && buf[5] == 0x66)
        send_favicon(connfd);

    close(connfd);
}



/******************************************************************************
* function : show usage
******************************************************************************/
static void *thread_httpd(void *arg)
{
    int listenfd = -1;
    unsigned short port = 80;
    int connfd = -1;

    struct sockaddr_in client;
    int client_len = sizeof(client);

    //绑定监听端口
    listenfd = startup(&port);
    printf("httpd fd %d running on port %d\n", listenfd, port);

    while (1)
    {
        //loop waiting for client connection
        connfd = accept(listenfd, (struct sockaddr *)&client, (socklen_t *)&client_len);
        if (connfd == -1) {
            //error_die("accept");
            error_die("accept");
        }

        //处理请求
        
        accept_request(connfd);
        //printf("receive connection connfd %d\n", connfd);
        //accept_request(connfd);

        /*if (pthread_create(&newthread , NULL, accept_request, connfd_sock) != 0)
          perror("pthread_create");*/
    }
    //关闭监听描述符,注意在这之前需要关闭close(connfd)
    close(listenfd);

    return(0);
}

int httpd()
{
    int ret = 0;
    pthread_t id;
    pthread_attr_t use_attr;

    (void)pthread_attr_init(&use_attr);
    use_attr.inheritsched = PTHREAD_EXPLICIT_SCHED;
    use_attr.detachstate = PTHREAD_CREATE_DETACHED;


    ret = pthread_create(&id, &use_attr, thread_httpd, "httpd");
    if (ret) {
        dprintf("httpd thread create failed, err code is %d\n", ret);
    }
    else
    {
        dprintf("httpd thread create SUCCESS\n");
    }
}
